#include "Player.h"
#include "RandomSystem.h"
#include "PlayerManager.h"
#include "Utilities.h"
#include "Team.h"
#include "ZStream.h"

#include <string>
#include <iostream>
#include <iomanip>
#include <cmath>


void PlayerSkills::Serialize(ZStream& o_stream)
{
	o_stream.WriteOpenTag("Skill");
	o_stream.WriteAttribute("goalkeeping",	m_roleSkills[Goalkeeper]);
	o_stream.WriteAttribute("defense",		m_roleSkills[Defender]);
	o_stream.WriteAttribute("attack",		m_roleSkills[Attacker]);
	o_stream.WriteAttribute("resistance",	m_resistance);
	o_stream.WriteAttribute("fatigue",		m_fatigue);
	o_stream.WriteCloseTag("Skill");
}

void PlayerSkills::Deserialize(ZStream& i_stream)
{
	i_stream.SkipLine();
	i_stream.ReadAttribute(m_roleSkills[Goalkeeper]);
	i_stream.ReadAttribute(m_roleSkills[Defender]);
	i_stream.ReadAttribute(m_roleSkills[Attacker]);
	i_stream.ReadAttribute(m_resistance);
	i_stream.ReadAttribute(m_fatigue);
	i_stream.SkipLine();
}

PlayerParams Player::s_params;

Player::Player(const std::string& i_name, const size_t i_id)
{
	Generate(i_name, i_id);
}

void Player::Generate(const std::string& i_name, const size_t i_id)
{
	m_daysUnavailable = 0;
	m_numOfInjury = 0;
	
	m_scoredGoal = 0;
	m_historyScoredGoal = 0;
	m_playedMatch = 0;
	m_historyPlayedMatch = 0;

	m_numSeason = 0;
	m_retirementYear = -1;

	m_id = i_id;
	m_age = MultiRand(10,2) + 15;
	m_name = i_name.substr(0, s_params.MAX_CHAR_PLAYER_NAME);
	m_team = 0;
	
	m_skill.m_roleSkills[Goalkeeper] = RandIntBetween(1, 99);
	m_skill.m_roleSkills[Defender] = RandIntBetween(1, 99);
	m_skill.m_roleSkills[Attacker] = RandIntBetween(1, 99);
	m_skill.m_resistance = RandIntBetween(1, 99);
	m_skill.m_fatigue = 0;

	std::swap(Utilities::Max4(m_skill.m_roleSkills[Goalkeeper], m_skill.m_roleSkills[Defender], m_skill.m_roleSkills[Attacker], m_skill.m_resistance), m_skill.m_resistance);
	
	const int diceRoll = rand()%100;
	SetRole( diceRoll >= 60 ? Attacker : (diceRoll >= 20 ? Defender : Goalkeeper) );

	CalculateSalary();
}

void Player::SetRole(const int i_role)
{
	assert(i_role >= 0 && i_role < NUM_ROLES);
	
	m_skill.m_bestRole = i_role;
	
	if(i_role == Goalkeeper)
		std::swap(Utilities::Max3(m_skill.m_roleSkills[Goalkeeper], m_skill.m_roleSkills[Defender], m_skill.m_roleSkills[Attacker]), m_skill.m_roleSkills[Goalkeeper]);
	else if(i_role == Defender)
		std::swap(Utilities::Max3(m_skill.m_roleSkills[Goalkeeper], m_skill.m_roleSkills[Defender], m_skill.m_roleSkills[Attacker]), m_skill.m_roleSkills[Defender]);
	else
		std::swap(Utilities::Max3(m_skill.m_roleSkills[Goalkeeper], m_skill.m_roleSkills[Defender], m_skill.m_roleSkills[Attacker]), m_skill.m_roleSkills[Attacker]);
}

const std::string& Player::GetTeamName() const
{
	static const std::string FreeTeamName = "--Free--";
	static std::string RetiredTeamName;
	const Team* playerTeam = GetTeam();
	if(playerTeam)
		return playerTeam->GetName();
	
	if(IsRetired())
	{
		RetiredTeamName = "--" + Utilities::IntToString(m_retirementYear) + "--";
		return RetiredTeamName;
	}

	return FreeTeamName;
}

// to be called at the end of the year
 bool Player::SeasonUpdate()
{
	EndSeason();
	return CheckRetirment();
}

void Player::EndSeason()
{
	++m_age;
	++m_numSeason;
	
	m_historyScoredGoal += m_scoredGoal;
	m_scoredGoal = 0;

	m_historyPlayedMatch += m_playedMatch;
	m_playedMatch = 0;

	m_skill.m_fatigue = 0;
	m_daysUnavailable = 0;

	CalculateSalary();
}

void Player::CalculateSalary()
{
	m_salary = static_cast<int>(
		(pow(static_cast<float>(m_skill.m_roleSkills[Attacker])/100.0f , 3) + 
		pow(static_cast<float>(m_skill.m_roleSkills[Defender])/100.0f , 3) + 
		pow(static_cast<float>(m_skill.m_roleSkills[Goalkeeper])/100.0f ,3) + 
		pow(static_cast<float>(m_skill.m_resistance)/200.0f ,2)) 
		* 50.f);
	m_salary = std::max(m_salary,1);
}

bool Player::CheckRetirment()const
{
	const int skill = GetAverageSkill();
	int percent = (m_age-30)*4;
	percent += m_numOfInjury/2;
	percent += (50-skill)/3;
	percent += (m_team ? 0 : m_age/2);
	const int diceRoll = rand()%100;
	if(diceRoll < percent)
		return true; //for breakpoints... else it's better "return diceRoll < percent;"
	return false;
}

bool Player::InjuryTest(const bool i_isTired)
{
	int chance = 1;
	chance += m_numOfInjury/5;
	chance += m_skill.m_fatigue/5;
	chance += (m_age>28 ? (m_age-28)/3 : 0); // 31,32,33 -> +1  34,35,36 -> +2  37,38,39 -> +3 ...
	chance += (i_isTired ? 5 : 0);

	const int diceRoll=rand()%200;
	if (diceRoll < chance)
	{
		++m_numOfInjury;

		int old_injury = m_daysUnavailable; //injury become worst

		const int difference = chance - diceRoll;
		const int normal = rand()%(difference+1);
		const int critical = m_age/3 - rand()%100;
		const int newInjury = 1 + std::max(normal, critical);
		m_daysUnavailable = std::min(newInjury + old_injury, MAX_DAY_OFF);
		return true;
	}

	return false;
}

float Player::CalculateStamina() const
{
	return (m_daysUnavailable == 0) ? m_skill.m_resistance - 0.01f * pow(static_cast<float>(m_skill.m_fatigue), 1.25f) : 0;
}

void Player::Heal()
{
	if (m_daysUnavailable > 0) 
		--m_daysUnavailable;
	if (m_skill.m_fatigue > 10)
		m_skill.m_fatigue -= 10;
	else
		m_skill.m_fatigue = 0;
}

void Player::PostMatchUpdateSkills(const int i_playedRole)
{
	for(int i=0; i< NUM_ROLES; ++i)
		PostMatchUpdateSkill(m_skill.m_roleSkills[i], i_playedRole == i, i_playedRole != NUM_ROLES);
	PostMatchUpdateResistance(i_playedRole != NUM_ROLES);
	m_skill.CalculateBestRole();
}

void Player::PostMatchUpdateSkill(int& o_skill, const bool i_up, const bool i_play)
{
	assert(i_play || !i_up);

	int diceRoll = RandIntBetween(-20,120);

	diceRoll += 25-m_age;
	diceRoll += (i_up ? 10 : (i_play ? 0  : -5));

	TryUpSkill(o_skill, diceRoll);
}

void Player::PostMatchUpdateResistance(const bool i_up)
{
	int diceRoll = RandIntBetween(-10,110);

	int ageDiff = 25-m_age;
	int ageFactor = ageDiff;
	do
	{
		ageDiff += 5;
		if(ageDiff<0)
			ageFactor += ageDiff;
	}while(ageDiff<0);

	diceRoll += ageFactor;
	diceRoll += i_up ? 5 : -5;
	
	TryUpSkill(m_skill.m_resistance, diceRoll);
}

void Player::TryUpSkill(int& o_skill, const int i_diceRoll)
{
	if(i_diceRoll < 0)
	{
		const int downShot = RandIntBetween(1, 50);
		if(downShot < o_skill)
			--o_skill;
	}
	else if(i_diceRoll > 100)
	{
		const int upShot = RandIntBetween(101, 160);
		if(upShot > o_skill)
			++o_skill;
	}
}

ZStream& Player::Serialize(ZStream& o_stream)
{
	o_stream.WriteOpenTag("Player");
	o_stream.WriteAttribute("ID",				m_id);
	//o_stream.WriteAttribute("Skill",			m_skill);
	m_skill.Serialize(o_stream);				
	o_stream.WriteAttribute("Age",				m_age);
	o_stream.WriteAttribute("Name",				m_name);
	o_stream.WriteAttribute("DaysUnavailable",	m_daysUnavailable);
	o_stream.WriteAttribute("NumOfInjury",		m_numOfInjury);
	o_stream.WriteAttribute("Salary",			m_salary);
	o_stream.WriteCloseTag("Player");

	return o_stream;
}

ZStream& Player::Deserialize(ZStream& i_stream)
{
	i_stream.SkipLine();
	i_stream.ReadAttribute(m_id);
	//i_stream.ReadAttribute(m_skill);
	m_skill.Deserialize(i_stream);
	i_stream.ReadAttribute(m_age);
	i_stream.ReadAttribute(m_name);
	i_stream.ReadAttribute(m_daysUnavailable);
	i_stream.ReadAttribute(m_numOfInjury);
	i_stream.ReadAttribute(m_salary);
	i_stream.SkipLine();

	return i_stream;
}

